package edu.ilstu.mind.ise.module.arduino;

import java.util.Map;

import edu.ilstu.mind.ise.ccp.message.Message;
import edu.ilstu.mind.ise.ccp.message.MessageFieldFormatException;
import edu.ilstu.mind.ise.ccp.message.MutableMessage;
import edu.ilstu.mind.ise.ccp.module.AbstractModule;
import edu.ilstu.mind.ise.module.arduino.alias.ArduinoAliasManager;
import edu.ilstu.mind.ise.module.arduino.controller.ArduinoController;
import edu.ilstu.mind.ise.module.arduino.controller.ArduinoControllerConnector;
import edu.ilstu.mind.ise.module.arduino.controller.ArduinoException;
import edu.ilstu.mind.ise.module.arduino.controller.PinMode;

/**
 * The Arduino Control Module. Exposes I/O of an Arduino board over the CCP.
 * 
 * @author Eric Thill
 * 
 */

public class ArduinoControlModule extends AbstractModule {

	public static final String PROP_KEY_COM_PORT = "COM_PORT";
	public static final String PROP_VALUE_AUTO_DETECT = "AUTO_DETECT";
	public static final String PROP_KEY_BOARD_INDEX = "BOARD INDEX";
	public static final String PROP_KEY_ALIASES = "ALIASES";

	public static final String FIELD_KEY_SUBJECT = "SUBJECT";
	public static final String FIELD_KEY_PIN_ALIAS = "PIN";
	public static final String FIELD_KEY_VALUE = "VALUE";
	public static final String FIELD_VALUE_SUBJECT_DIGITAL_WRITE = "DIGITAL_WRITE";
	public static final String FIELD_VALUE_SUBJECT_ANALOG_WRITE = "ANALOG_WRITE";
	public static final String FIELD_VALUE_SUBJECT_DIGITAL_READ = "DIGITAL_READ";
	public static final String FIELD_VALUE_SUBJECT_ANALOG_READ = "ANALOG_READ";

	private final String comPort;
	private final int boardIndex;
	private final Map<String, String> aliases;

	private ArduinoAliasManager arduinoAliasManager;

	@SuppressWarnings("unchecked")
	public ArduinoControlModule(Map<String, Object> props) throws ArduinoException {
		comPort = props.get(PROP_KEY_COM_PORT).toString();
		if (PROP_VALUE_AUTO_DETECT.equals(comPort)) {
			boardIndex = (Integer) props.get(PROP_KEY_BOARD_INDEX);
		} else {
			boardIndex = -1;
		}
		this.aliases = (Map<String, String>) props.get(PROP_KEY_ALIASES);
	}

	@Override
	public void init() {
		// create the Arduino Controller
		getLogger().info("Creating ArduinoController");
		String comPort = this.comPort;
		if (PROP_VALUE_AUTO_DETECT.equals(comPort)) {
			getLogger().info("Auto-Detecting ArduinoController");
			comPort = ArduinoControllerConnector.autoDetect(boardIndex);
		}
		getLogger().info("Connecting to COM Port <" + comPort + ">");
		ArduinoController controller = ArduinoControllerConnector.connect(comPort);

		// create Arduino Alias Manager
		getLogger().info("Creating ArduinoAliasManager");
		arduinoAliasManager = new ArduinoAliasManager(controller);

		// Set all of the aliases in the Arduino Alias Manager
		for (Map.Entry<String, String> entry : aliases.entrySet()) {
			// Create "line" variable for logging in multiple places
			String line = "<" + entry.getKey() + "=" + entry.getValue() + ">";
			// parse the alias key, pin id value and pin mode value
			getLogger().info("Parsing " + line);
			String alias = entry.getKey();
			String[] valueSplit = entry.getValue().split(",");
			if (valueSplit.length != 2) {
				throw new IllegalArgumentException("ALIAS map value format: alias=pinId,pinMode");
			}
			String pinId = valueSplit[0];
			PinMode pinMode = PinMode.valueOf(valueSplit[1]);
			// set the alias and pin mode
			getLogger().info("Setting " + line);
			try {
				arduinoAliasManager.createAlias(pinId, alias, pinMode);
			} catch (ArduinoException e) {
				getLogger().error("Could not create alias and set pin " + line, e);
			}
		}
	}

	@Override
	protected boolean onMessage(Message message) {
		String pinAlias = message.getStringField(FIELD_KEY_PIN_ALIAS);
		if (pinAlias == null) {
			return false;
		}

		try {
			// process message depending on subject
			String subject = message.getStringField(FIELD_KEY_SUBJECT);
			if (FIELD_VALUE_SUBJECT_DIGITAL_WRITE.equals(subject)) {
				// write to a digital pin
				boolean value = message.getBooleanField(FIELD_KEY_VALUE);
				arduinoAliasManager.digitalWrite(pinAlias, value);
			} else if (FIELD_VALUE_SUBJECT_ANALOG_WRITE.equals(subject)) {
				// write to an analog pin
				int value = message.getIntField(FIELD_KEY_VALUE);
				arduinoAliasManager.analogWrite(pinAlias, value);
			} else if (FIELD_VALUE_SUBJECT_DIGITAL_READ.equals(subject)) {
				// respond with a digital value from a digital read
				boolean value = arduinoAliasManager.digitalRead(pinAlias);
				respondValue(message.getFrom(), pinAlias, value);
			} else if (FIELD_VALUE_SUBJECT_ANALOG_READ.equals(subject)) {
				// respond with an analog value from an analog read
				int value = arduinoAliasManager.analogRead(pinAlias);
				respondValue(message.getFrom(), pinAlias, value);
			}
		} catch (ArduinoException e) {
			getLogger().error("Arduino error while processing message", e);
		} catch (MessageFieldFormatException e) {
			getLogger().error("Invalid message field format", e);
		}
		return false;
	}

	private void respondValue(String to, String pinAlias, boolean value) {
		// respond to a request for a digital value
		MutableMessage msg = createMessage(to, 3);
		msg.setField(FIELD_KEY_SUBJECT, FIELD_VALUE_SUBJECT_DIGITAL_READ);
		msg.setField(FIELD_KEY_PIN_ALIAS, pinAlias);
		msg.setField(FIELD_KEY_VALUE, value);
		sendMessage(msg);
	}

	private void respondValue(String to, String pinAlias, int value) {
		// respond to a request for an analog value
		MutableMessage msg = createMessage(to, 3);
		msg.setField(FIELD_KEY_SUBJECT, FIELD_VALUE_SUBJECT_ANALOG_READ);
		msg.setField(FIELD_KEY_PIN_ALIAS, pinAlias);
		msg.setField(FIELD_KEY_VALUE, value);
		sendMessage(msg);
	}

}
